﻿// 游戏状态
const GameStatus = {
    END: 0,     // 已结束
    PLAYING: 1  // 游戏中
}

/**
 * 移动结果类的构造方法。
 * @param box       被推动的箱子
 * @param direction 移动方向
 */
function MoveNode(box, direction) {
    /**
     * 获取箱子。
     * @return 箱子
     */
    this.getBox = function() {
        return box
    }

    /**
     * 获取方向。
     * @return 方向
     */
    this.getDirection = function() {
        return direction
    }
}

/**
 * 游戏类的构造方法。
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 */
function Game(drawer, gameBoard) {
    var mBoxCount = 0                                           // 未到达目的地的箱子数
    const mDrawer = drawer                                      // 绘图者
    const mGameBoard = gameBoard                                // 游戏区域
    const mGameOverEvent = document.createEvent("customEvent")  // 游戏结束事件
    const mGotoLevelEvent = document.createEvent("customEvent") // 进入关卡事件
    var mLevel = 1                                              // 当前关卡
    var mMan = null                                             // 小人
    var mMoveCount = 0                                          // 移动次数
    const mManMovedEvent = document.createEvent("customEvent")  // 小人移动事件
    var mMoveList = null                                        // 所有移动结点
    var mStatus = GameStatus.END                                // 状态

    // 初始化成员
    mGameOverEvent.initEvent(Const.UM_GAME_OVER, false, false)
    mGotoLevelEvent.initEvent(Const.UM_GOTO_LEVEL, false, false)
    mManMovedEvent.initEvent(Const.UM_MAN_MOVED, false, false)

    // 进入第1关
    enterLevel()

    /**
     * 进入关卡。
     */
    function enterLevel() {
        // 小人停止跳舞
        if (mMan != null) {
            mMan.stopDancing()
        }

        // 重绘游戏区域
        mDrawer.fillStyle = Const.GAME_BOARD_COLOR
        mDrawer.fillRect(0, 0, Const.GAME_BOARD_WIDTH, Const.GAME_BOARD_HEIGHT)

        // 删除所有物体
        gBaseGameObjects = new Array(Const.GAME_BOARD_GRID_COUNT_V)
        for (var i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            gBaseGameObjects[i] = new Array(Const.GAME_BOARD_GRID_COUNT_H)
        }

        // 删除所有移动结点
        mMoveList = new Array()

        // 创建所有物体
        var type = -1
        mBoxCount = 0
        eval("var levelData =" + Const.LEVEL_NAME + mLevel + " ")
        var x = 0, y = 0
        for (var i = 0; i < levelData.length; i++) {
            if (levelData[i] == "0") {
                x++
                continue
            }
            if (levelData[i] == "\n") { // 下一行
                x = 0
                y++
                continue
            }

            type = parseInt(levelData[i])

            if (type == GameObjectType.BOX) {
                // 创建箱子
                gBaseGameObjects[y][x] = new Box(new BaseGameObject(mDrawer, mGameBoard, GameObjectType.BACKGROUND, x, y),
                    mDrawer, mGameBoard, type, x, y)
                mBoxCount++
            } else if (type == GameObjectType.MAN) {
                // 创建小人
                gBaseGameObjects[y][x] = mMan = new Man(new BaseGameObject(mDrawer, mGameBoard, GameObjectType.BACKGROUND, x, y),
                    mDrawer, mGameBoard, type, x, y)
            } else {
                // 创建背景、目的地、墙
                gBaseGameObjects[y][x] = new BaseGameObject(mDrawer, mGameBoard, type, x, y)
            }

            x++
        }

        // 重置成员变量
        mMoveCount = 0
        mStatus = GameStatus.PLAYING

        // 发送通知
        mGotoLevelEvent.arg1 = mLevel
        document.dispatchEvent(mGotoLevelEvent)
    }

    /**
     * 游戏结束。
     * @param allOver   全部通关则为 true，否则为 false
     */
    function gameOver(allOver) {
        // 将状态设置为已结束
        mStatus = GameStatus.END

        // 发送通知
        mGameOverEvent.arg1 = allOver
        document.dispatchEvent(mGameOverEvent)
    }

    /**
     * 获取当前关卡数。
     * @return 当前关卡数
     */
    this.getLevel = function() {
        return mLevel
    }

    /**
     * 进入指定的关卡。
     * @param level 关卡
     */
    this.gotoLevel = function(level) {
        if (level <= Const.LEVEL_COUNT) {
            mLevel = level
            enterLevel()
        } 
    }

    /**
     * 进入下一关。
     */
    function gotoNextLevel() {
        if (mLevel < Const.LEVEL_COUNT) {
            mLevel++
            enterLevel()
        }
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    function isWon() {
        return mBoxCount == 0
    }

    /**
     * 按键事件的响应方法。
     * @param key   按键
     */
    this.onKeyPressed = function(key) {
        // 如果没在玩，则直接返回
        if (mStatus != GameStatus.PLAYING) {
            return
        }

        // 如果按的不是方向键，则直接返回
        if (key < Const.KEY_LEFT || key > Const.KEY_DOWN) {
            return
        }
        
        // 移动小人。如果不能移动，则直接返回
        const moveResult = new MoveResult()
        const movedBox = mMan.move(mMan, key, moveResult)
        if (moveResult.result == _MoveResult.NO_MOVE) {
            return
        }

        // 重绘游戏区域
        mGameBoard.repaint()

        // 增加移动次数
        mMoveCount++

        // 发送通知
        mManMovedEvent.arg1 = mMoveCount
        document.dispatchEvent(mManMovedEvent)

        // 判断是否胜利
        if (moveResult.result == _MoveResult.ARRIVED_ON_DEST) {
            mBoxCount--
            if (isWon()) {
                let allOver = mLevel == Const.LEVEL_COUNT
                gameOver(allOver) 
                if (!allOver) {
                    gotoNextLevel()
                }
                return
            }
        } else if (moveResult.result == _MoveResult.LEFT_FROM_DEST) {
            mBoxCount++
        }

        // 将本次移动加入列表
        mMoveList.push(new MoveNode(movedBox, key))
    }

    /**
     * 重玩本关。
     */
    this.restart = function() {
        gotoLevel(mLevel)
    }

    /**
     * 回退，即撤消上一次操作。
     */
    this.undo = function() {
        if (mMoveCount == 0) {
            return
        }
               
        // 计算方向（方向与最后一次移动的方向相反）
        var direction = 0
        const moveListNode = mMoveList.pop()
        switch (moveListNode.getDirection()) {
            case Const.KEY_DOWN:
                direction = Const.KEY_UP
                break
            case Const.KEY_LEFT:
                direction = Const.KEY_RIGHT
                break
            case Const.KEY_RIGHT:
                direction = Const.KEY_LEFT
                break
            case Const.KEY_UP:
                direction = Const.KEY_DOWN
                break
        }

        // 移动小人
        const moveResult = new MoveResult()
        mMan.move(mMan, direction, moveResult)
        
        // 移动箱子
        const box = moveListNode.getBox()
        if (box != null) {
            box.move(box, direction, moveResult)
            if (moveResult.result == _MoveResult.LEFT_FROM_DEST) {
                mBoxCount++
            } else if (moveResult.result == _MoveResult.ARRIVED_ON_DEST) {
                mBoxCount--
            }
        }
        
        // 发送通知
        mMoveCount--
        mManMovedEvent.arg1 = mMoveCount
        document.dispatchEvent(mManMovedEvent)
    }
}
